Spring Cloud Config新手教程及源码分析
小编近期参与的项目与微服务有关,谈到微服务,大家自然会想到 Spring Cloud 的一系列子框架。今天,小编为大家带来小火同学投稿的《Spring Cloud Config 新手教程及源码分析》,本文用示例细致的讲解了Spring Cloud Config的使用步骤和方法,并且给出了部分源码分析。
叮~~~!一起来听课吧!
一、背 景
我们知道,一般情况下,一个应用通常经历开发阶段(dev),集成测试阶段(sit),用户验收测试阶段(uat),最后才能安心的上生产(prod),每个阶段由于负责人和软硬件环境的变更,很可能会使用不同的配置参数,最常见的场景就是程序员所使用的数据库密码和QA所使用的以及最后生产环境的密码肯定是要严格控制的,那么衍生出来的需求就是同一份应用在不同的环境下运行需要不同的配置文件。这对于传统的单体应用来说并没有什么困难,但是采用微服务架构的场景下,由于服务的不断拆分,需要管理的配置文件的数量迅速增加,如果团队很时髦,使用了gitflow,那么很可能每一个feature或者hotfix分支都需要一份额外的配置文件,那简直会爆炸。不过莫慌,万能的spring团队给我们准备了神器spring-cloud-config,我们一起来看一下它是怎么解决我们的需求的。
二、使用
要明确的是,spring-cloud-config在宏观上分成三个角色,第一个角色是config-repo,是实际上存放配置文件的地方,第二个是config-server,配置中心的服务端,由它来管理config-repo,并接受每个client获取配置的请求,最后一个自然是config-client,它通常是我们的微服务应用,它会被告知server的位置,并在初始化的过程中向server请求配置文件。如此一来,配置文件可以集中得到管理,便捷且安全;应用在启动的过程中只要通过几个环境变量就可以拿到当前环境下的配置文件,清爽方便。
闲言少叙,我们一步步来搭建这三个模块。首先是config-repo,实际上就是一个文件夹,我们在github上将其初始化好。
接下来是config-server,它是一个spring cloud项目,这里使用maven去构建(推荐尝试gradle,更加简洁清爽),pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.daocloud</groupId>
<artifactId>config-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>config-server</name>
<description>Spring Cloud project</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Brixton.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
作为一个标准的springboot项目,我们只需要在入口处打开@EnableConfigServer即可赋予它config-server身份,可谓开箱即用:
@EnableConfigServer
@SpringBootApplication
public class ConfigServerApplication {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApplication.class).web(true).run(args);
}
}
最后是它的配置文件application.properties:
spring.application.name=config-server
server.port=9091
spring.cloud.config.server.git.uri=https://github.com/
spring.cloud.config.server.git.searchPaths=repo
spring.cloud.config.server.git.username=username
spring.cloud.config.server.git.password=password
spring.cloud.config.server.bootstrap=true
可以看到我们为其指定了应用名(方便我们以后利用服务治理实现高可用的配置中心集群),监听的端口,config-repo资源的地址和访问该资源的账号和密码等。最后是config-client,我们只需要在一般的业务应用中加入spring-cloud-client 依赖即可赋予它client身份,同样是开箱即用,贴一下我们在一个实际项目中使用的build.gradle:
group 'cloud.config.com'
version '1.0-SNAPSHOT'
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
classpath "io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE"
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: "io.spring.dependency-management"
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-config:1.3.1.RELEASE'
}
}
dependencies {
compile "org.flywaydb:flyway-core:4.0.3"
compile "org.springframework.boot:spring-boot-starter-web:1.5.1.RELEASE"
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa', version: '1.5.1.RELEASE'
compile group: 'mysql', name: 'mysql-connector-java', version: '6.0.5'
compile 'org.projectlombok:lombok:1.16.10'
compile group: 'com.google.guava', name: 'guava', version: '19.0'
compile "org.hibernate:hibernate-validator"
compile 'biz.paluch.redis:lettuce:4.3.1.Final'
testCompile group: 'junit', name: 'junit', version: '4.12'
testCompile "org.springframework.boot:spring-boot-starter-test"
compile group: 'org.json', name: 'json', version: '20160810'
compile group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.8.0'
compile 'org.springframework.cloud:spring-cloud-starter-config'
}
下面划重点,当应用拥有client身份后,它在启动的时候就不再需要依赖application.properties或者application.yml,我们只需要在bootstrap.properties文件中或者命令行以环境变量的形式传入下面三个参数,应用在启动的过程中 就会自动从client-server拿到相应的配置文件,相当方便。
spring.application.name=name
spring.cloud.config.profile=dev
spring.cloud.config.uri=http://localhost:9091/ #config-server的位置
具体来说,上面的配置会让该应用向config-server请求名称为nev-name.properties(或nev-name.yml)的配置,这样我们只要简单的变更spring.cloud.config.profile参数就可以达到在不同环境下获取相应配置文件的目的,是不是简单好用?
三、原理分析
下面我们从源码的角度简要的去分析config-client和config-server的工作机制。
config client
首先关注一下引导配置类ConfigServiceBootstrapConfiguration,它的作用是在SpringBoot上下文启动时读取resources目录下的bootstrap.properties文件,取出前缀名为spring.cloud.config的配置项,这些配置项通常包含config server的节点信息、连接该节点所需要的权限配置以及客户端所要抓取的配置信息,并以此初始化ConfigClientProperties,
@Bean
public ConfigClientProperties configClientProperties() {
ConfigClientProperties client = new ConfigClientProperties(this.environment);
return client;
}
@Bean
@ConditionalOnProperty(value = "spring.cloud.config.enabled", matchIfMissing = true)
public ConfigServicePropertySourceLocator configServicePropertySource() {
ConfigServicePropertySourceLocator locator = new ConfigServicePropertySourceLocator(
configClientProperties());
return locator;
}
进而实例化ConfigServicePropertySourceLocator,而后者实质上是一个属性资源定位器,其locate方法拥有通过http的方式向config服务端请求配置文件的功能。
另一个重要的类是PropertySourceBootstrapConfiguration,它实现了ApplicationContextInitializer接口,该接口会在应用上下文刷新之前(refresh())被回调,从而做一些初始化动作,简易的函数调用栈如下:
SpringApplicationBuilder.run() -> SpringApplication.run() -> SpringApplication.createAndRefreshContext() -> SpringApplication.applyInitializers() -> PropertySourceBootstrapConfiguration.initialize()
而上述ConfigServicePropertySourceLocator的locate方法会在initialize中被调用,从而保证上下文在refresh之前能够拿到必要的配置信息。
config server
同client一样,我们首先分析引导配置类ConfigServerBootstrapConfiguration,他的作用同样是初始化资源定位器EnvironmentRepositoryPropertySourceLocator,后者可以从一个叫做EnvironmentRepository的地方获取配置。
默认情况下这个引导配置类是关闭状态,原因是它可能会延迟整个项目的启动,我们可以显式的配置spring.cloud.config.server.bootstrap=true使其生效。EnvironmentRepository类似于其他应用的dao层,它对配置文件的访问做了一层抽象,一共有三类,分别支持从本地、svn以及git获取配置文件。而直接接受client请求的则是EnvironmentController,它实际上和我们业务应用中的RestController一样,get到url中的参数并依此调用EnvironmentRepository.find()方法,最后将配置返回给client端,典型的代码如下:
@RequestMapping("/{name}/{profiles}/{label:.*}")
public Environment labelled(@PathVariable String name, @PathVariable String profiles,
@PathVariable String label) {
if (label != null && label.contains("(_)")) {
// "(_)" is uncommon in a git branch name, but "/" cannot be matched
// by Spring MVC
label = label.replace("(_)", "/");
}
Environment environment = this.repository.findOne(name, profiles, label);
return environment;
}
通过debugger可以发现,这里的Environment实际上是EnvironmentEncryptorEnvironmentRepository,它自己不会去获取配置而是委托给成员变量MultipleJGitEnvironmentRepository去做,它只负责后续必要的解码工作(是的,我们可以对配置文件进行加密使其更安全!):
@Override
public Environment findOne(String name, String profiles, String label) {
Environment environment = this.delegate.findOne(name, profiles, label);
if (this.environmentEncryptor != null) {
environment = this.environmentEncryptor.decrypt(environment);
}
if (!this.overrides.isEmpty()) {
environment.addFirst(new PropertySource("overrides", this.overrides));
}
return environment;
}
MultipleJGitEnvironmentRepository继承自JGitEnvironmentRepository,不用怀疑它的作用就是通过发起HTTP或者SSH连接去config-repo获取配置文件。
如此一来我们大致可以将config-client,config-server和config-repo串连起来,反过来再去理解一下这套工具的使用,是不是觉得水到渠成。但是实际上spring-cloud-config还具有很多其它让人惊喜的功能,比如本地缓存配置文件,在一定程度上允许你的server挂一会儿,比如加解密功能,让配置文件的管理和传输更加安全放心,甚至你还可以通过某些方法在client运行时动态的去刷新配置,连变更配置文件都不需要重启了,是不是很酷!期待大家对spring-cloud-config的深入挖掘!
欢迎任何技术爱好者投稿!